《编写可读代码的艺术》阅读笔记(三)

重新组织代码

一、抽取不相关的子问题

建议:积极发现并抽取出不相关的子逻辑

  • 看看某个函数或代码块,问问自己,这段代码高层次目标是什么?
  • 对每一行代码,问问自己,它是直接为了目标而工作吗?这段代码的高层次目标是什么?
  • 如果有足够的行数在解决不相关的子问题,抽取代码到独立的函数中
  • 例子,计算地球上两个点的最近距离
    // Return which element of 'array' is closest to the given latitude/longitude.
    // Models the Earth as a perfect sphere.
    var findClosestLocation = function(lat, lng, array) {
    var closest;
    var closest_dist = Number.MAX_VALUE;
    for (var i = 0; i < array.length; i += 1) {
    // Convert both points to radians.
    var lat_rad = radians(lat);
    var lng_rad = radians(lng);
    var lat2_rad = radians(array[i].latitude);
    var lng2_rad = radians(array[i].longitude);
    // Use the "Spherical Law of Cosines" formula.
    var dist = Math.acos(Math.sin(lat_rad) * Math.sin(lat2_rad) +
    Math.cos(lat_rad) * Math.cos(lat2_rad) *
    Math.cos(lng2_rad - lng_rad));
    if (dist < closest_dist) {
    closest = array[i];
    closest_dist = dist;
    }
    }
    return closest;
    };

循环中大部分代码都在解决一个和高层问题无关的子问题:“计算两个坐标之间的球面距离”,所以我们将它抽离出来:

var spherical_distance = function(lat1, lng1, lat2, lng2) {
var lat1_rad = radians(lat1);
var lng1_rad = radians(lng1);
var lat2_rad = radians(lat2);
var lng2_rad = radians(lng2);
// Use the "Spherical Law of Cosines" formula.
return Math.acos(Math.sin(lat1_rad) * Math.sin(lat2_rad) +
Math.cos(lat1_rad) * Math.cos(lat2_rad) *
Math.cos(lng2_rad - lng1_rad));
};

然后函数就变成了,可读性更高,还能做单独测试:

var findClosestLocation = function(lat, lng, array) {
var closest;
var closest_dist = Number.MAX_VALUE;
for (var i = 0; i < array.length; i += 1) {
var dist = spherical_distance(lat, lng, array[i].latitude, array[i].longitude);
if (dist < closest_dist) {
closest = array[i];
closest_dist = dist;
}
}
return closest;
};

值得明白的一点是,例子中并不是在讲怎么架构,不是从如何解耦出发的,而是从如何抽取不相关的问题出发的。虽然殊途同归,但是思路是不一样的。

  • 意料之外的好处:当抽取不相关子问题成为独立的代码之后,改进这些独立的代码变得更加容易。

  • 过犹不及:过多的抽离子问题,会让整个代码引入过多的小函数,阅读者就需要在不同的函数之间反复跳跃,降低可读性。

二、一次只做一件事

建议

  1. 列出代码所做的所有任务
  2. 尽量把任务拆分到不同的函数中,或者至少是代码中不同的段落中

三、把想法变代码

建议

  1. 像对着一个同事一样用自然语言描述代码要做什么
  2. 注意描述中所用的关键词和短语
  3. 写出与描述所匹配的代码

清楚描述逻辑

在写代码之前,先清晰的描述(写)出你的逻辑,再进行coding,这样有助于你写出更好的代码。
这不就是传说中的“小黄鸭debug术”吗!

下面这个例子就说明了如何用清晰的逻辑重构代码:

$is_admin = is_admin_request();
if ($document) {
if (!$is_admin && ($document['username'] != $_SESSION['username'])) {
return not_authorized();
}
} else {
if (!$is_admin) {
return not_authorized();
}
}
// continue rendering the page ..

可以将上述逻辑表述为:

// 以下两种情况之一,你就能获得授权:
// 1. 你是管理员
// 2. 你拥有当前文档(如果有当前文档的话)
// 否则,无法授权与你

于是,代码可以重构为:

if (is_admin_request()) {
// authorized
} else if ($document && ($document['username'] == $_SESSION['username'])) {
// authorized
} else {
return not_authorized();
}
// continue rendering the page ...

三、少写代码

最好读的代码就是:没有代码

质疑和拆分需求

并不是所有程序都要运行得快,100%准确,并且能处理所有的输入。可以根据实际需要,拆分并且削减一部分需求。

保持小的代码库

建议

  • 代码复用,创建更多可复用的“工具”代码
  • 减少无用的代码或没有用的功能
  • 让代码保持分开的子项目状态

熟悉你周边的库

建议:每个一段时间,花15分钟来阅读标准库中的所有函数/模块/类型的名字,用以了解有什么可以用,下次写新的代码时就会想到。